﻿//Copyright 2011 - Jonathan Allen

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Diagnostics.Contracts;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;

namespace Granite.Modeling.Internals
{
	internal class ErrorsDictionary
	{
		readonly Dictionary<string, IList<ValidationResult>> m_Errors = new Dictionary<string, IList<ValidationResult>>();


		/// <summary>
		/// Clears all errors
		/// </summary>
		/// <returns>Returns True if this actually removed any errors from the collection.</returns>
		public ErrorsDictionaryUpdateType Clear()
		{
			var result = HasErrors();
			m_Errors.Clear();
			if (result)
				return ErrorsDictionaryUpdateType.HasErrorsIsFalse;
			else
				return ErrorsDictionaryUpdateType.NoChange;
		}


		/// <summary>
		/// Sets the errors at the propery level
		/// </summary>
		/// <param name="propertyName"></param>
		/// <param name="errors"></param>
		/// <returns></returns>
		public ErrorsDictionaryUpdateType SetErrors(string propertyName, IList<ValidationResult> errors)
		{
			Contract.Requires(propertyName != null);
			Contract.Requires(errors != null);

			bool differences;

			if (m_Errors.ContainsKey(propertyName)) //check for differences
			{
				var oldErrors = m_Errors[propertyName];
				Contract.Assume(oldErrors != null);

				if (oldErrors.Count != errors.Count)
					differences = true;
				else
					differences = !oldErrors.SequenceEqual(errors, ValidationResultEqualityComparer.Default);

			}
			else
				differences = true;

			if (differences)
			{
				var oldHasErrors = HasErrors();
				m_Errors[propertyName] = errors;
				var newHasErrors = HasErrors();

				if (oldHasErrors == newHasErrors)
					return ErrorsDictionaryUpdateType.ErrorsChanged;
				else if (newHasErrors == true)
					return ErrorsDictionaryUpdateType.HasErrorsIsTrue;
				else
					return ErrorsDictionaryUpdateType.HasErrorsIsFalse;
			}
			else
				return ErrorsDictionaryUpdateType.NoChange;
		}

		/// <summary>
		/// Set the errors at the object level
		/// </summary>
		/// <param name="errors"></param>
		/// <param name="affectedProperties"></param>
		/// <returns></returns>
		public ErrorsDictionaryUpdateType SetErrors(IList<ValidationResult> errors, out HashSet<string> affectedProperties)
		{
			Contract.Requires(errors != null);

			bool differences;
			affectedProperties = new HashSet<string>();

			foreach (var p in (from err in errors from prop in err.MemberNames select prop))
				affectedProperties.Add(p);

			if (m_Errors.ContainsKey("")) //check for differences
			{
				var oldErrors = m_Errors[""];
				Contract.Assume(oldErrors != null);

				if (oldErrors.Count != errors.Count)
					differences = true;
				else
					differences = !oldErrors.SequenceEqual(errors, ValidationResultEqualityComparer.Default);

				foreach (var p in (from err in oldErrors from prop in err.MemberNames select prop))
					affectedProperties.Add(p);
			}
			else
				differences = true;

			if (differences)
			{
				var oldHasErrors = HasErrors();
				m_Errors[""] = errors;
				var newHasErrors = HasErrors();

				if (oldHasErrors == newHasErrors)
					return ErrorsDictionaryUpdateType.ErrorsChanged;
				else if (newHasErrors == true)
					return ErrorsDictionaryUpdateType.HasErrorsIsTrue;
				else
					return ErrorsDictionaryUpdateType.HasErrorsIsFalse;
			}
			else
				return ErrorsDictionaryUpdateType.NoChange;
		}


		[Pure]
		public bool HasErrors()
		{
			return m_Errors.Values.Any(list => list.Count > 0);
		}


		[Pure]
		internal ReadOnlyCollection<ValidationResult> GetErrors(string propertyName)
		{
			Contract.Ensures(Contract.Result<ReadOnlyCollection<ValidationResult>>() != null);

			if (propertyName == null)
				propertyName = "";

			IEnumerable<ValidationResult> errors;

			//Fetch the property-level errors
			if (m_Errors.ContainsKey(propertyName))
				errors = m_Errors[propertyName];
			else
				errors = Enumerable.Empty<ValidationResult>();

			//Add the object-level errors related to this property
			if (!string.IsNullOrEmpty(propertyName) && m_Errors.ContainsKey(""))
			{
				var objectErrors = m_Errors[""];
				Contract.Assume(errors != null);
				Contract.Assume(objectErrors != null);
				errors = errors.Union(from e in objectErrors where e.MemberNames.Contains(propertyName) select e);
			}

			Contract.Assume(errors != null);

			return new ReadOnlyCollection<ValidationResult>(errors.ToList());
		}

		[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode"), SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
		[ContractInvariantMethod]
		void ObjectInvariant()
		{
			Contract.Invariant(m_Errors != null);
		}
	}

	/// <summary>
	/// This is used to indicate whether or not the errors collection was actually updated. 
	/// </summary>
	internal enum ErrorsDictionaryUpdateType
	{
		NoChange = 0,
		HasErrorsIsTrue = 1,
		HasErrorsIsFalse = 2,
		ErrorsChanged = 3
	}


#if WINDOWS_PHONE
	internal class HashSet<T> : List<T>
	{
		//TODO: Make this into a real hashset
	}
#endif


}
